(ns
  com.marzhillstudios.molehill.indexer
  (:import [org.joda.time DateTime])
  (:use [clojure.contrib.duck-streams :only [with-out-writer file-str]]
        [com.marzhillstudios.molehill.file :only [files-from-list
                                                  add-parsed-content
                                                  hill-file-type]]
     [clojure.contrib.logging]
     [com.marzhillstudios.util :only [add-type]]
     [com.marzhillstudios.molehill.utils :only [distinctp]])
  (:gen-class))


(defstruct index-update :value :key :request)

(defn mk-update-request [value keyname request]
  (struct-map index-update :value value :key keyname :request request))

; TODO(jwall): move to marzhillstudios.util
(defn now [] (.getMillis (DateTime.)))

; private api
(defn mk-index-map
  ([]
   (hash-map))
  ([index-key index-vals]
    (hash-map index-key (vec index-vals))))

(defn update-index-map
  [index-map index-update-request]
  (debug (str "index-map: " (pr-str index-map)
             "index-update-request: " (pr-str index-update-request)))
  (let [index-key (:key index-update-request)
        [ts value] (:value index-update-request)
        request (:request index-update-request)
        [_ old-entries] (find index-map index-key)]
    (cond
      (= request :delete)
        (assoc index-map index-key (vec (sort-by #(first %)
                                     (filter #(not (= value (second %))) old-entries))))
      (= request :update)
        (assoc index-map index-key
               (vec (sort-by #(first %)
                             (distinctp #(second %) 
                               (cons [ts value] old-entries))))))))

(defn do-update-index
  [index-map index-entries]
  (cond
    (empty? index-entries)
      (do
        (info (str "No entries to update index with"))
        nil)
    :else
      (do
        (debug (str "Updating index with requests"))
        (debug (str "index entry requests: "
                    (pr-str index-entries)))
        (reduce (fn [i-map update]
                (update-index-map i-map update))
                 index-map
                 index-entries))))

; public api
(defn get-index-from-file
  "Read in a current index from a file."
  [index-name]
  (let [file (file-str index-name)]
    (condp #(and %1 %2) (.exists file)
      (.isFile file) (or (load-file index-name) (mk-index-map)) ; theres our index :-)
      (not (.isFile file)) (throw (IllegalStateException. ; whoah mama bad file
                                   (format
                                    "index file: [%s] exists and is not a regular file"
                                    index-name)))
      (mk-index-map)))) ; the default if no clause matches is an empty index

(defn get-index-date-sorted
  ([index-name index-key] (get-index-date-sorted index-name index-key >))
  ([index-name index-key pred]
    (sort-by #(first %) pred (index-key (get-index-from-file index-name)))))

(defn write-index-to-file
  "Read in a current index from a file."
  [index-file index-map]
  (debug (str "writing index: " index-map))
  (with-out-writer (file-str index-file)
                   (pr index-map)))

(defn update-index
  "Updates a molehill index with new entries.

  index-entries => [index-update-requests]"
  [index-file index-entries]
  (let [index-map (get-index-from-file index-file)]
    (debug (str "Loaded index: " (pr-str index-map)))
    (write-index-to-file index-file 
      (do-update-index index-map index-entries))))

(defn files-from-index
  [index-entries]
  (assert (not (nil? index-entries)))
  ; TODO(jwall): the below massaging should really be in the file namespace
  (map #(add-type hill-file-type
                  (add-parsed-content (second %1)))
       index-entries))

;  Example index structure:
;  {
;    index-key1: [hillfile1 hillfile2]
;    index-key2: [hillfile1 hillfile2]
;  }
